1.1 使用 Flutter webview_flutter 库引入 WebView 能力

WebView 是移动开发中最常使用的控件之一,也是最为复杂的控件之一。在 Flutter 开发中,也需要 WebView 能力。因此,Google 官方团队开发了 webview_flutter 库,它利用 PlatformView 机制,对 Android、iOS 的 WebView 进行封装,封装为一个 Flutter 组件。开发者在使用时,如同使用纯 Flutter 组件一般,而在底层,是嵌入了原生 WebView 视图能力。

桌面端支持

Flutter PlatformView 在桌面端的支持并不完善,因此长期以来 webview_flutter 局限于 Android、iOS 平台

从 Flutter 3.24 开始,macOS 下 PlatformView 趋于完善,webview_flutter 已可运行于 macOS 平台。具体参见《Flutter macOS WebView(3.24 后)


WebPage

引入 webview_flutter 库后,WebView 的页面实现如下。采用 Flutter 下常用的 Controller 模式——WebViewWidget 组件负责展示,WebViewController 负责控制。我们使用 StatefulWidget。

class WebViewExample extends StatefulWidget {
  const WebViewExample({super.key});

  @override
  State<WebViewExample> createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<WebViewExample> {
  // Webview 控制器,很多功能都通过它实现
  late final WebViewController _controller;

  // 创建 WebView 的初始化参数,不同平台不一样
  late final PlatformWebViewControllerCreationParams params;

  @override
  void initState() {
    super.initState();
    
    // WebViewPlatform.instance 插件初始化时会自动赋值,不同平台类型不同
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }

    // 工厂方法创建 WebViewController
    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);

	// controller 通过链式方法进行配置
    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      // 略....
      ..addJavaScriptChannel(
        'Toaster',
        onMessageReceived: (JavaScriptMessage message) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        },
      )
      // 发起请求
      ..loadRequest(Uri.parse('https://flutter.dev'));

	// 保存 controller 实例,后续需传入 WebViewWidget
    _controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      appBar: AppBar(/* 略... */)
      // WebView 视图组件
      body: WebViewWidget(controller: _controller),
    );
  }
  // ...
}

其中,包含两个核心元素:


WebViewWidget

这是 webview_flutter 封装好后的视图组件。WebViewWidget 是一个 StatelessWidget,主要实现如下:

class WebViewWidget extends StatelessWidget {
  WebViewWidget({
    Key? key,
    required WebViewController controller,
    TextDirection layoutDirection = TextDirection.ltr,
    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers =
        const <Factory<OneSequenceGestureRecognizer>>{},
  }) : this.fromPlatformCreationParams(
          key: key,
          params: PlatformWebViewWidgetCreationParams(
            // 实际需要的是 controller 中的 platform
            controller: controller.platform,
            layoutDirection: layoutDirection,
            gestureRecognizers: gestureRecognizers,
          ),
        );

  // 在 PlatformWebViewWidget 构造方法中,又访问到 WebViewPlatform.instance
  // WebViewPlatform.instance 在不同平台下,有不同的实现
  WebViewWidget.fromPlatformCreationParams({
    Key? key,
    required PlatformWebViewWidgetCreationParams params,
  }) : this.fromPlatform(key: key, platform: PlatformWebViewWidget(params));

  /// 最终的实例构造方法,需要一个 key,一个 PlatformWebViewWidget
  WebViewWidget.fromPlatform({super.key, required this.platform});

  /// Implementation of [PlatformWebViewWidget] for the current platform.
  final PlatformWebViewWidget platform;

  // ...

  @override
  Widget build(BuildContext context) {
	// 可以看到,PlatformWebViewWidget 自身并非组件,而是一个工厂
    return platform.build(context);
  }
}

综合来看,WebViewWidget 根据传入的平台,创建 PlatformWebViewWidget 实例,PlatformWebViewWidget 内部会根据 WebViewPlatform.instance 在不同平台下,有不同的实现。

PlatformWebViewWidget 自身是一个工厂,通过 build 方法创建出不同平台下的 Flutter Webview Widget。

PlatformWebViewWidget 的实现类们

PlatformWebViewWidget 是个抽象类,通过上一节分析可知,不同平台下,有 PlatformWebViewWidget 的不同实现类,具体包括:

webview_flutter 一共就支持这三个平台。

PlatformWebViewWidget.build

下面我们来看他们的 build 方法:

AndroidWebViewWidget.build:

在《Flutter 内嵌原生视图 Android 端接入实现》中提到,Flutter 在 Android 下 PlatformView 有 3 中显示模式,其中 Hybrid compositionTexture Layer Hybrid Composition 都需要使用 PlatformViewLink 组件进行封装。

下面的 PlatformViewLink 具体实现,可以与《Flutter 内嵌原生视图 Android 端接入实现》中官方文档中给出的默认实现相对比,来帮助理解。

@override
Widget build(BuildContext context) {
  // ...
  return PlatformViewLink(
    // Setting a default key using `params` ensures the `PlatformViewLink`
    // recreates the PlatformView when changes are made.
    key: _androidParams.key ??
        ValueKey<AndroidWebViewWidgetCreationParams>(
            params as AndroidWebViewWidgetCreationParams),
    viewType: 'plugins.flutter.io/webview',
    surfaceFactory: (
      BuildContext context,
      PlatformViewController controller,
    ) {
      return AndroidViewSurface(
        controller: controller as AndroidViewController,
        gestureRecognizers: _androidParams.gestureRecognizers,
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: (PlatformViewCreationParams params) {
      return _initAndroidView(
        params,
        displayWithHybridComposition:
            _androidParams.displayWithHybridComposition,
        platformViewsServiceProxy: _androidParams.platformViewsServiceProxy,
        view:
            (_androidParams.controller as AndroidWebViewController)._webView,
        instanceManager: _androidParams.instanceManager,
        layoutDirection: _androidParams.layoutDirection,
      )
        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
        ..create();
    },
  );

其中引人注意的是这一段:

return _initAndroidView(
  params,
  displayWithHybridComposition:
      _androidParams.displayWithHybridComposition,
  platformViewsServiceProxy: _androidParams.platformViewsServiceProxy,
  view:
      (_androidParams.controller as AndroidWebViewController)._webView,
  instanceManager: _androidParams.instanceManager,
  layoutDirection: _androidParams.layoutDirection,
)

这段代码关系到 webview_flutter 以何种模式进行渲染,尤其是 displayWithHybridComposition 这个设置选项,表示开发者是否强制开启 Hybrid composition 模式(具体参见《Flutter webview_flutter Android 端实现原理》)。

下面看 _initAndroidView 的实现:

AndroidViewController _initAndroidView(
  PlatformViewCreationParams params, {
  //...
}) {
  final int? instanceId = instanceManager.getIdentifier(view);

  // 默认为 false
  if (displayWithHybridComposition) {
    // Flutter 3.0 后,该方法表示 Flutter Hybrid composition|Hybrid composition
    return platformViewsServiceProxy.initExpensiveAndroidView(
      id: params.id,
      viewType: 'plugins.flutter.io/webview',
      layoutDirection: layoutDirection,
      creationParams: instanceId,
      creationParamsCodec: const StandardMessageCodec(),
    );
  } else {
    // Flutter 3.0 后,该方法表示 Texture Layer Hybrid Composition
    return platformViewsServiceProxy.initSurfaceAndroidView(
      id: params.id,
      viewType: 'plugins.flutter.io/webview',
      layoutDirection: layoutDirection,
      creationParams: instanceId,
      creationParamsCodec: const StandardMessageCodec(),
    );
  }
}

这两个 init 方法都是代理方法,分别看一下他们的具体实现:

platformViewsServiceProxy.initExpensiveAndroidView

/// Proxy method for [PlatformViewsService.initExpensiveAndroidView].
ExpensiveAndroidViewController initExpensiveAndroidView({
  required int id,
  required String viewType,
  required TextDirection layoutDirection,
  dynamic creationParams,
  MessageCodec<dynamic>? creationParamsCodec,
  VoidCallback? onFocus,
}) {
  // 调用 Flutter 框架 API
  return PlatformViewsService.initExpensiveAndroidView(
    id: id,
    viewType: viewType,
    layoutDirection: layoutDirection,
    creationParams: creationParams,
    creationParamsCodec: creationParamsCodec,
    onFocus: onFocus,
  );
}

platformViewsServiceProxy.initSurfaceAndroidView

SurfaceAndroidViewController initSurfaceAndroidView({
  required int id,
  required String viewType,
  required TextDirection layoutDirection,
  dynamic creationParams,
  MessageCodec<dynamic>? creationParamsCodec,
  VoidCallback? onFocus,
}) {
  // 调用 Flutter 框架 API
  return PlatformViewsService.initSurfaceAndroidView(
    id: id,
    viewType: viewType,
    layoutDirection: layoutDirection,
    creationParams: creationParams,
    creationParamsCodec: creationParamsCodec,
    onFocus: onFocus,
  );
}

legacy WebView

在 webview_flutter 中,还包含一套已经废弃的旧实现 WebView.

目前,在新实现中,WebView 的功能由 WebViewControllerWebViewWidget 所继任。

因此,我们需要注意,不要使用 WebView 了,而是按照本文开头方式,或者文档进行使用。

网络资源


本文作者:Maeiee

本文链接:1.1 使用 Flutter webview_flutter 库引入 WebView 能力

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!